package com.imooc.pay.service.impl;

import com.imooc.pay.dao.PayInfoDao;
import com.imooc.pay.entity.PayInfo;
import com.imooc.pay.enums.OrderStatusEnums;
import com.imooc.pay.service.PayService;
import com.lly835.bestpay.config.AliPayConfig;
import com.lly835.bestpay.config.WxPayConfig;
import com.lly835.bestpay.enums.BestPayPlatformEnum;
import com.lly835.bestpay.enums.BestPayTypeEnum;
import com.lly835.bestpay.enums.OrderStatusEnum;
import com.lly835.bestpay.model.PayRequest;
import com.lly835.bestpay.model.PayResponse;
import com.lly835.bestpay.service.impl.BestPayServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;

@Service("payService")
public class PayServiceImpl implements PayService {

    Logger logger = LoggerFactory.getLogger(PayServiceImpl.class);

    @Resource(name = "wxPayConfig")
    private WxPayConfig wxPayConfig;

    @Resource(name = "aliPayConfig")
    private AliPayConfig aliPayConfig;

    @Resource(name = "payInfoDao")
    private PayInfoDao payInfoDao;

    @Resource(name = "bestPayService")
    private BestPayServiceImpl bestPayService;

    @Override
    public PayResponse create(String orderId, String payPlatform, BigDecimal amount) throws RuntimeException {

        //发起支付
        //发起支付时，要将相应的订单信息写入数据库，比如订单号，金额之类的。
        //在这一步有必要针对恶意请求及恶意下单的情况处理一下
        //暂时认为，应该通过业务系统处理
        // 通过用户的ip，user-agent, 可以做相应时间限制用户发起的请求次数
        PayInfo payInfo = new PayInfo(Long.valueOf(orderId), Integer.valueOf(payPlatform), amount, OrderStatusEnum.NOTPAY.getDesc());
        //支付系统的订单号必须是唯一，业务系统的订单号也必须唯一
        //收到业务系统传来的订单号之后，不要直接将订单号写入数据库
        //因为当业务系统出现异常时，该订单号已经生成，不能修改，
        //所以业务系统可能会多次传来同一个订单号，因此，支付系统要
        //将业务系统的订单号通过映射从新生成一个新的支付系统的订单号，再写入数据库
        //但这样又有新的问题，那就是会容易造成用户支付两次或多次，
        //所以在发起第二次支付前，先将第一次支付关闭
        //具体可以通过检测业务系统的订单号是否和上次一样，
        //如果一样，则调用支付平台(API)关闭第一次支付，然后发起第二次支付。
        PayInfo temp = payInfoDao.selectByOrderId(Long.valueOf(orderId));
        if (temp == null) {
            int i = payInfoDao.insertSelective(payInfo);
            if (i <= 0) {
                throw new RuntimeException("订单创建失败");
            }
        } else {
            if (temp.getPlatformStatus().equals(OrderStatusEnums.SUCCESS)) {
                throw new RuntimeException("订单:"+OrderStatusEnums.SUCCESS);
            }
        }
        boolean wxFlag = !payPlatform.equals("1")&!payPlatform.equals("2");
        if(wxFlag) {
            RuntimeException exception = new RuntimeException();
            exception.initCause(new RuntimeException("暂不支持的支付类型"));
            throw exception;
        }
        PayRequest request = new PayRequest();
        request.setOrderName("刘才模开发的支付页面");
        request.setOrderId(orderId);
        request.setOrderAmount(amount.doubleValue());
        //BestPayServiceImpl是支付类，里面包含支付的方法
        if (payPlatform.equals("1")) {
            // 1 = 微信native支付
            bestPayService.setWxPayConfig(wxPayConfig);
            request.setPayTypeEnum(BestPayTypeEnum.WXPAY_NATIVE);
        } else if (payPlatform.equals("2")) {
            // 2 =支付宝 网页&移动应用
            bestPayService.setAliPayConfig(aliPayConfig);
            request.setPayTypeEnum(BestPayTypeEnum.ALIPAY_PC);
        }

        PayResponse response = bestPayService.pay(request);
        logger.info("LiuCaiMo-生成支付二维码={}",response);
        return response;
    }

    @Override
    public String asyncNotify(String notifyData) {
        //1.签名校验
        PayResponse payResponse = bestPayService.asyncNotify(notifyData);
        logger.info("LiuCaiMo-异步通知={}",payResponse);

        //2.金额校验
        //从数据库中查询该订单涉及的金额，再和异步通知返回的response响应体的订单，金额是否一致
        PayInfo payInfo = payInfoDao.selectByOrderId(Long.valueOf(payResponse.getOrderId()));
        //这个方法是用来接收异步通知的，当收到异步通知时，说明已经支付成功
        //所以payInfo不可能为null
        if (payInfo == null) {
            //这是非常严重的错误(正常情况下是不可能发生的)
            //在抛出这个异常前程序应该通过钉钉api接口或短信方式告警开发人员
            throw new RuntimeException("order_number查询结果为null");
        }
        //3.如果订单和金额的校验通过，就应该修改订单的支付状态
        if (!payInfo.getPlatformStatus().equals(OrderStatusEnums.SUCCESS)) {
            //BigDecimal有自己的金额比较方法，其重写了compareTo方法
            if (payInfo.getAmount().compareTo(BigDecimal.valueOf(payResponse.getOrderAmount()))!=0) {
                //非常严重的错误，告警开发人员
                //收到异步通知基本代表支付成功，正常情况不会出现金额不一致的情况
                throw new RuntimeException("异步通知中的金额与数据库中订单金额不一致，order_number=" + payInfo.getOrderNumber());
            }
            //修改订单支付状态
            payInfo.setPlatformStatus(OrderStatusEnums.SUCCESS.getInfo());
            payInfo.setPlatformNumber(payResponse.getOutTradeNo());
            payInfoDao.updateByPrimaryKeySelective(payInfo);
        }

        //TODO 支付系统完成支付后，还要通过MQ给业务系统改善通知，
        // 暂时写这里，待业务系统完成后回来写这里的代码

        //4.告诉微信支付系统，不要再给我(商户的支付系统)发送异步通知了。
        //这部分内部是返回给微信支付系统的，在controller中应该使用@ResponseBody，
        //哪里访问了这个控制器，这个控制器就把数据返回到哪里，
        //如果是浏览器访问了这个控制器就把数据返回给浏览器，
        //如果是某服务器访问了这个控制器，就把数据返回给某服务器。
        //所以不能在controller中使用ModelAndView，因为它会直接跳转至浏览器，并渲染页面。
        //而是使用@ResponseBody。
        if (payResponse.getPayPlatformEnum() == BestPayPlatformEnum.WX) {
            return "<xml>\n" +
                    "  <return_code><![CDATA[SUCCESS]]></return_code>\n" +
                    "  <return_msg><![CDATA[OK]]></return_msg>\n" +
                    "</xml> ";
        } else if(payResponse.getPayPlatformEnum() == BestPayPlatformEnum.ALIPAY){
            return "success";
        }
        throw new RuntimeException("不会出现的意外情况");
    }

    @Override
    public PayInfo selectByOrderId(Long orderId) {
        return payInfoDao.selectByOrderId(orderId);
    }


}
